<?php

namespace common\filters;

use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\StringHelper;
use yii\web\UnauthorizedHttpException;
use common\rest\DownloadAction;

/**
 * HttpBearerAuth is an action filter that supports the authentication method based on HTTP Bearer token.
 *
 * @author emhome <emhome@163.com>
 * @since 1.0
 */
class HttpBearerAuth extends \yii\filters\auth\HttpBearerAuth {

    /**
     * @var integer the Http authentication user's primary key ID.
     */
    public $uid = 0;

    /**
     * @var string the HTTP authentication user's name.
     */
    public $username;

    /**
     * @var AppToken|null the HTTP authentication token
     */
    public $user;

    /**
     * @var integer the Http authentication user's primary key ID.
     */
    public $build = false;

    /**
     * @var string the HTTP authentication realm
     */
    public $filterAccessToken = true;

    /**
     * @var array the HTTP authentication realm
     */
    public $accessAllows = [];

    /**
     * @var array the HTTP authentication realm
     */
    public $exposeHeaders = [];

    /**
     * @var string the HTTP authentication token
     */
    public $identityClass;

    /**
     * @inheritdoc
     */
    public function beforeAction($action) {
        $this->build = $action->controller->build;

        $request = $this->request ?: Yii::$app->getRequest();
        $response = $this->response ?: Yii::$app->getResponse();

        if ($this->identityClass) {
            $user = new $this->identityClass;
        } else {
            $user = $this->user ?: Yii::$app->getUser();
        }
        try {
            $identity = $this->authenticate($user, $request, $response);
        } catch (UnauthorizedHttpException $e) {
            if ($this->checkAllows($action)) {
                return true;
            }
            throw $e;
        }
        if ($action instanceof DownloadAction) {
            $this->exposeHeaders = ArrayHelper::merge($this->exposeHeaders, [
                'Content-Disposition'
            ]);
        }
        if ($identity !== null) {
            $this->bindAuthenticate($identity);
            $this->bindVersion($response);
            $this->bindExposeHeaders($response);
            return true;
        }
        if ($this->checkAllows($action)) {
            return true;
        }
        $this->challenge($response);
        $this->handleFailure($response);
        return false;
    }

    /**
     * @inheritdoc
     */
    protected function checkAllows($action) {
        if ($this->isOptional($action) && !$this->isCrossDomain()) {
            return true;
        }
        if ($this->isPublicRoute($action) || $this->filterAccessToken === false) {
            return true;
        }
        return false;
    }

    /**
     * Checks, whether authentication is optional for the given action.
     *
     * @param Action $request action to be checked.
     * @return bool whether authentication is optional or not.
     * @see optional
     * @since 2.0.7
     */
    protected function isCrossDomain() {
        $request = $this->request ?: Yii::$app->getRequest();
        $allowOrigins = ArrayHelper::getValue(Yii::$app->params, 'crossDomains', []);
        if (!$request->origin || in_array($request->origin, $allowOrigins)) {
            return false;
        }
        return true;
    }

    /**
     * @param \yii\base\Action $action Description
     * @inheritdoc
     */
    public function isPublicRoute($action) {
        $id = $action->getUniqueId();
        foreach ($this->accessAllows as $pattern) {
            if (StringHelper::matchWildcard($pattern, $id)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @inheritdoc
     */
    public function bindAuthenticate($identity) {
        $this->uid = $identity->getId();
        $this->user = $identity;
    }

    /**
     * @param \yii\web\Response $response Description
     * @inheritdoc
     */
    public function bindExposeHeaders($response) {
        $exposeHeaders = (array) $this->exposeHeaders;
        if (empty($exposeHeaders)) {
            return;
        }
        foreach ($exposeHeaders as $name => $content) {
            if (!$content) {
                continue;
            }
            if (is_numeric($name)) {
                $response->getHeaders()->add('Access-Control-Expose-Headers', $content);
            } else {
                $response->getHeaders()->add('Access-Control-Expose-Headers', $name);
                $response->getHeaders()->set($name, $content);
            }
        }
    }

    /**
     * @param \yii\web\Response $response Description
     * @inheritdoc
     */
    public function bindVersion($response) {
        $response->getHeaders()->set('Access-Control-Expose-Headers', 'Build');
        $response->getHeaders()->set('Build', $this->build);
    }

}
